大家好!我是2魚,今天我們要深入探討後端開發中兩個核心概念:API(應用程式介面)和CRUD(創建、讀取、更新、刪除)操作。這兩個概念是構建現代Web應用的基石。我們將從MongoDB資料庫設置開始,逐步實現一個完整的API,涵蓋所有CRUD操作。無論你是後端新手還是想複習基礎的開發者,這篇文章都會給你實用的洞見和hands-on經驗。讓我們開始這段精彩的後端之旅吧!
首先,我們需要確保MongoDB Atlas中的測試環境是乾淨的。這個步驟很重要,因為它可以幫助我們避免舊數據干擾新的開發過程。
登入MongoDB Compass
找到你的test資料庫
刪除所有現有的集合
這樣做可以讓我們從一個乾淨的狀態開始開發,避免之前的測試數據影響我們的新功能開發。
models
資料夾KEY | 英文欄位名稱 | 中文欄位名稱 | 型態 | 必填 | 預設值 | 備註 |
---|---|---|---|---|---|---|
PK | _id | ID | ObjectID | v | ||
contactPerson | 名稱 | String | v | |||
phone | 電話 | String | ||||
信箱 | String | v | ||||
content | 內容 | String | v | |||
source | 從哪裡得知本網站 | Number | 1:網路搜尋 2:親友推薦 3:社群媒體 4:其他 | |||
createTime | 創建時間 | Date | now |
feedback.js
文件引入 Mongoose 套件
const mongoose = require("mongoose");
這行程式碼的作用是引入 mongoose
這個工具,它能幫助我們與 MongoDB 資料庫進行溝通,並且管理資料。
定義聯絡我們模型
const feedbackSchema = new mongoose.Schema(
{
// 名稱
contactPerson: { type: String, required: [true, "姓名未填寫"] },
// 電話
phone: { type: String },
// 信箱
email: { type: String, required: [true, "信箱未填寫"] },
// 內容
feedback: { type: String, required: [true, "內容未填寫"] },
// 從哪裡得知此網站
source: {
type: String,
enum: ["網路搜尋", "社群媒體", "親友介紹", "其他"],
},
},
{
versionKey: false,
timestamps: true,
}
);
這段程式碼的目的是創建一個「模型」,用來描述我們想在資料庫裡儲存的「聯絡我們」資料的結構,簡而言之,就是這些資料的「樣板」。
每個欄位的說明:
-contactPerson
(名稱):用於儲存用戶的名字。type: String
表示它是文字格式,而且必須填寫(required: true
),如果沒填寫就會顯示「姓名未填寫」的錯誤訊息。
-phone
(電話):用於儲存用戶的電話號碼。它是文字格式,但這個欄位可以不填(沒有required
)。
-
-feedback
(內容):用於儲存用戶給你的反饋內容。這也是必填項,沒填寫的話會顯示「內容未填寫」。
-source
(從哪裡得知此網站):用於記錄用戶是從哪裡知道你這個網站的。它只能是幾個選項之一(例如「網路搜尋」、「社群媒體」等)。
其他設定:
-versionKey: false
:這個設定讓 Mongoose 不會自動生成一個叫__v
的版本號欄位。
-timestamps: true
:這個設定會自動幫你添加兩個欄位,分別記錄資料創建和最後更新的時間。
創建並導出模型
const Feedback = mongoose.model("Feedback", feedbackSchema);
module.exports = Feedback;
最後,我們使用 mongoose.model
創建了一個名為 Feedback
的模型,這個模型就像是我們剛剛定義的資料「樣板」的具體實現。
接著,我們用 module.exports
將這個模型導出,這樣在其他地方的程式碼中也可以使用它。
app.js
進行測試app.js
添加以下程式碼:
const Feedback = require("./models/feedback");
npm start
執行專案💡 補充說明:為什麼新增的是
feedbacks
而不是feedback
當你使用 Mongoose 創建模型時,Mongoose 會自動將模型名稱轉換為複數形式,用來命名 MongoDB 中的集合。這是 Mongoose 的預設行為。
### 在上面的範例:
- 我們定義了一個模型叫Feedback
。
- Mongoose 會把Feedback
變成feedbacks
,然後在資料庫裡建立這個集合。
- 所以,當我們把資料存進去時,它會自動進入feedbacks
這個集合,而不是feedback
。
這樣的設計是因為資料庫通常會存放多筆資料,所以用複數形式來命名會更符合實際情況。
### 如果你不想要複數形式:
如果你希望集合名稱保持單數,你可以在創建模型時指定具體的集合名稱:
```jsx
// 這是上面範例寫法,建立出來的集合會是 feedbacks
const Feedback = mongoose.model("Feedback", feedbackSchema);
// 自訂集合名稱,撰寫在 () 第三個變數,建立出來會是 feedback
const Feedback = mongoose.model("Feedback", feedbackSchema, "feedback");
```
這樣資料就會存到名為 `feedback` 的集合裡,而不是 `feedbacks`。
這種自動複數化的行為是 Mongoose 幫你做的,但你也可以輕鬆地控制它。
接下來,我們將在 Express 專案中實現 CRUD(創建、讀取、更新、刪除)操作。
我們會以上面定義的 Feedback 模型為例,直接在 routes 中實現這些功能。
const mongoose = require("mongoose");
const feedbackSchema = new mongoose.Schema(
{
contactPerson: { type: String, required: [true, "姓名未填寫"] },
phone: { type: String },
email: { type: String, required: [true, "信箱未填寫"] },
feedback: { type: String, required: [true, "內容未填寫"] },
source: {
type: String,
enum: ["網路搜尋", "社群媒體", "親友介紹", "其他"],
},
},
{
versionKey: false,
timestamps: true,
}
);
const Feedback = mongoose.model("Feedback", feedbackSchema);
module.exports = Feedback;
routes
資料夾中創建 feedback.js
檔案,並實現 CRUD 操作:首先,我們需要設置基本的路由結構:
const express = require("express");
const router = express.Router();
const Feedback = require("../models/feedback");
// 這裡我們將添加我們的 CRUD 操作
module.exports = router;
現在,讓我們逐一實現 CRUD 操作:
新增 feedback
// 新增 feedback
router.post("/", async (req, res) => {
try {
// 從請求的 body 中提取出用戶提交的資料(姓名、電話、信箱、反饋內容)
const { contactPerson, phone, email, feedback } = req.body;
// 驗證必填欄位,檢查是否提供了姓名、信箱和反饋內容
if (!contactPerson || !email || !feedback) {
// 如果缺少必填欄位,回應 400 錯誤狀態和錯誤信息
return res.status(400).json({
status: "fail",
message: "姓名、信箱、內容為必填欄位",
});
}
// 如果必填欄位都有,使用 Feedback 模型創建一條新的反饋記錄
const newFeedback = await Feedback.create({
contactPerson: contactPerson, // 設置聯絡人姓名
phone: phone, // 設置聯絡人電話(可選)
email: email, // 設置聯絡人信箱
feedback: feedback, // 設置反饋內容
});
// 成功創建後,回應 201 狀態(已創建)和成功信息,包含新創建的反饋數據
res.status(201).json({
status: "success",
data: newFeedback,
});
} catch (error) {
// 如果在創建過程中發生錯誤,回應 400 錯誤狀態和錯誤信息
res.status(400).json({
status: "fail",
message: error.message,
});
}
});
router.post("/"...)
表示這是一個處理POST請求的路由,路徑為根路徑"/"。async/await
來處理非同步操作,確保資料庫操作完成後再回應。const { contactPerson, phone, email, feedback } = req.body;
從請求體中提取所需資料。Feedback.create()
方法創建新的feedback記錄。獲取所有 Feedback
// 取得所有 feedback
router.get("/", async (req, res) => {
try {
// 使用 find() 方法從資料庫中取得所有 feedbacks
const feedbacks = await Feedback.find();
// 成功取得資料後,回應 200 狀態碼和回傳的資料
res.status(200).json({
status: "success", // 狀態為成功
results: feedbacks.length, // 回傳的 feedbacks 數量
data: feedbacks, // 回傳所有 feedbacks 的資料
});
} catch (error) {
// 如果發生錯誤(如資料庫連接問題),回應 404 錯誤狀態和錯誤訊息
res.status(404).json({
status: "fail", // 狀態為失敗
message: error.message, // 返回錯誤訊息
});
}
});
Feedback.find()
方法不帶參數,會返回所有feedback記錄。獲取指定 ID 的 Feedback
// 取得指定 id feedback
router.get("/:id", async (req, res) => {
try {
// 使用 findById 方法從資料庫中取得指定 id 的 feedback
const feedback = await Feedback.findById(req.params.id);
// 如果找不到該 id 對應的資料,回應 404 錯誤狀態和錯誤訊息
if (!feedback) {
return res.status(404).json({
status: "fail", // 狀態為失敗
message: "找不到該 feedback", // 返回錯誤訊息
});
}
// 成功取得資料後,回應 200 狀態碼和回傳的資料
res.status(200).json({
status: "success", // 狀態為成功
data: feedback, // 回傳指定 id 的 feedback 資料
});
} catch (error) {
// 如果發生錯誤(如無效的 id),回應 404 錯誤狀態和錯誤訊息
res.status(404).json({
status: "fail", // 狀態為失敗
message: error.message, // 返回錯誤訊息
});
}
});
:id
是一個路徑參數,可以通過req.params.id
獲取。Feedback.findById()
方法查找特定ID的feedback。更新指定 ID 的 Feedback
// 更新指定 id feedback
router.patch("/:id", async (req, res) => {
try {
// 檢查必填欄位是否存在
const { contactPerson, email, feedback } = req.body;
if (!contactPerson || !email || !feedback) {
return res.status(400).json({
status: "fail",
message: "姓名、信箱、內容為必填欄位",
});
}
// 使用 findByIdAndUpdate 更新資料
const updatedFeedback = await Feedback.findByIdAndUpdate(
req.params.id, // 從 URL 參數中獲取要更新的資料 ID
req.body, // 使用請求 body 中的資料進行更新
{
new: true, // 返回更新後的資料
runValidators: true, // 在更新時執行驗證
}
);
// 如果找不到該資料,回應 404 錯誤
if (!updatedFeedback) {
return res.status(404).json({
status: "fail",
message: "找不到該 feedback",
});
}
// 成功更新後回應 200 狀態和更新後的資料
res.status(200).json({
status: "success",
data: updatedFeedback,
});
} catch (error) {
// 捕獲錯誤並返回 400 錯誤狀態和錯誤信息
res.status(400).json({
status: "fail",
message: error.message,
});
}
});
findByIdAndUpdate
方法同時查找和更新文檔。
new: true
選項返回更新後的文檔。runValidators: true
確保更新時執行模型的驗證規則。刪除指定 ID 的 Feedback
// 刪除指定 id feedback
router.delete("/:id", async (req, res) => {
try {
// 使用 findByIdAndDelete 根據 URL 中的 id 參數查找並刪除對應的 feedback
const feedback = await Feedback.findByIdAndDelete(req.params.id);
// 如果找不到對應的 feedback(即該 id 不存在),回應 404 錯誤
if (!feedback) {
return res.status(404).json({
status: "fail", // 狀態為失敗
message: "找不到該 feedback", // 返回錯誤訊息
});
}
// 如果成功刪除,回應 200 狀態碼,表示成功操作
res.status(200).json({
status: "success", // 狀態為成功
message: "刪除成功", // 返回成功訊息
data: null, // 雖然此處用 200,但不返回具體的資料,data 設為 null
});
} catch (error) {
// 如果發生錯誤(例如無效的 id),回應 400 錯誤狀態
res.status(400).json({
status: "fail", // 狀態為失敗
message: error.message, // 返回錯誤訊息,通常是錯誤的詳細信息
});
}
});
findByIdAndDelete
方法查找並刪除指定ID的文檔。data: null
,因為資源已被刪除。最後,別忘了在 app.js
中引入這個新的路由:
const feedbackRoute = require("./routes/feedback");
app.use("/feedbacks", feedbackRoute);
這樣,我們就完成了 Feedback 的 CRUD 操作!每個操作都有其特定的 HTTP 方法和路徑,使用了適當的 Mongoose 方法來操作數據庫。
在開發API時,正確使用HTTP狀態碼對於前後端溝通至關重要。以下是一些常用的狀態碼及其含義:
狀態碼 | 說明 | 使用情境 | 餐廳情境比喻 |
---|---|---|---|
200 OK | 請求成功,資料已回傳。 | GET取得資料、PUT/PATCH更新資料成功時。 | 服務生:「您的餐點已送達,請慢用。」 |
201 Created | 請求成功,新資源已建立。 | POST新增資料成功時。 | 櫃台:「您的訂位已成功,座位已準備好。」 |
204 No Content | 請求成功,但無需回傳資料。 | 刪除成功,或不需回傳內容的操作。 | 服務生收走空盤子,點頭示意但不說話。 |
400 Bad Request | 請求有誤,伺服器無法處理。 | 傳送的資料格式錯誤或缺少必要參數。 | 廚師:「這個訂單資訊不完整,請確認後再下單。」 |
401 Unauthorized | 需要驗證身份。 | 未登入或登入已過期。 | 櫃台:「對不起,這是會員專屬餐廳,請出示會員卡。」 |
403 Forbidden | 已驗證身份,但權限不足。 | 嘗試存取無權限的資源。 | 經理:「很抱歉,員工專用廚房區域不對外開放。」 |
404 Not Found | 請求的資源不存在。 | 訪問不存在的網址或資源。 | 服務生:「對不起,我們的菜單上沒有這道菜。」 |
500 Internal Server Error | 伺服器內部錯誤。 | 伺服器端程式錯誤或未預期的異常。 | 主廚:「非常抱歉,廚房設備突然故障,暫時無法處理訂單。」 |
正確使用 HTTP 狀態碼能大幅提升 API 的可用性和可維護性。它不只幫助開發者快速理解請求的結果,也有助於前端更有效地處理不同情況。選擇適當的狀態碼,就像在 API 和使用者之間建立了一個清晰的溝通橋樑。
記住,優秀的 API 設計不僅僅是實現功能,更重要的是提供清晰的溝通。適當使用狀態碼,就像為您的 API 安裝了一個高效的訊息系統,讓每次的請求和回應都能一目了然,大大提升了開發效率和用戶體驗。
在處理API刪除操作時常見的兩種狀態碼:200 OK 和 204 No Content。它們到底有啥不一樣?來,讓我給你講個簡單的例子:
想像你請你的好麻吉幫忙刪掉一張尷尬的路邊攤醉倒照片。你麻吉不只幫你刪掉,還很熱心地跟你說:「搞定囉!那張照片是上個月吃宵夜時拍的,檔案有5MB大欸!」
在API裡面,用200 OK就是這種感覺,不只說刪掉了,還會給你一堆有的沒的:
res.status(200).json({
message: "刪掉囉,放心啦",
deletedItem: {
name: "醉倒路邊攤.jpg",
date: "2023-05-01",
size: "5MB"
}
});
現在想像你的麻吉只是簡單回你:「OK啦」,什麼都沒多說。
在API裡,用204 No Content就是醬子的:
res.status(204).send();
這基本上就是跟客戶端說:「刪掉了啦,沒事了,掰掰」。
app.delete('/posts/:id/comments/:commentId', (req, res) => {
// 刪除推文的程式碼
res.status(204).send();
});
這裡啊,我們只要知道推文刪掉了就好,不用知道更多有的沒的。
app.delete('/files/:id', (req, res) => {
// 刪除檔案的程式碼
res.status(200).json({
message: "檔案刪掉囉",
deletedFile: {
name: "期末報告.docx",
size: "2.5MB",
lastEdited: "2023-06-15"
}
});
});
這個例子中,回傳被刪掉的檔案資訊可能對使用者有幫助,萬一誤刪還可以知道刪掉什麼。
好啦!我們的 API 小冒險暫時告一段落。從資料庫設置到 CRUD 操作,我們跑了一圈後端開發的小obstacle course。現在的你,就像剛學會騎腳踏車的小孩,已經可以自己平穩前進了!
記住,一個好的 API 就像一個好朋友:容易相處(易用性)、守信用(可靠性),而且總是能給出清楚的回應(狀態碼和錯誤訊息)。你現在已經認識了這位新朋友,接下來就是多多練習,讓你們的友誼更加深厚。
下一站,我們要認識一位新朋友:Postman。它就像是你的 API 健身教練,幫你檢查每個動作是否到位。準備好要再次揮灑汗水了嗎?
繼續保持熱情,開發的世界總有新奇等著你。現在,是時候歇口氣,喝杯咖啡,然後準備迎接下一個挑戰了!
大家有沒有想要更詳細補充的部分呢?歡迎在下方留言分享喔!我們一起學習、一起成長。明天見啦,掰掰~
(對了,如果你覺得今天的內容對你有幫助,別忘了給個讚支持一下喔!這會是我繼續努力的動力呢~)